/*-------------------------------------------------------------------------
 *
 * fe-secure.c
 *	  与设置到后端的安全连接相关的函数。
 *	  安全连接预计提供机密性、消息完整性和端点身份验证。
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/interfaces/libpq/fe-secure.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres_fe.h"

#include <signal.h>
#include <fcntl.h>
#include <ctype.h>

#ifdef WIN32
#include "win32.h"
#else
#include <sys/socket.h>
#include <unistd.h>
#include <netdb.h>
#include <netinet/in.h>
#ifdef HAVE_NETINET_TCP_H
#include <netinet/tcp.h>
#endif
#include <arpa/inet.h>
#endif

#include <sys/stat.h>

#ifdef ENABLE_THREAD_SAFETY
#ifdef WIN32
#include "pthread-win32.h"
#else
#include <pthread.h>
#endif
#endif

#include "fe-auth.h"
#include "libpq-fe.h"
#include "libpq-int.h"

/*
 * 宏用于处理禁用然后恢复 SIGPIPE 处理的状态。
 * 在 Windows 上，这些都是无操作，因为没有 SIGPIPEs。
 */

#ifndef WIN32

#define SIGPIPE_MASKED(conn)	((conn)->sigpipe_so || (conn)->sigpipe_flag)

#ifdef ENABLE_THREAD_SAFETY

struct sigpipe_info
{
	sigset_t	oldsigmask;
	bool		sigpipe_pending;
	bool		got_epipe;
};

#define DECLARE_SIGPIPE_INFO(spinfo) struct sigpipe_info spinfo

#define DISABLE_SIGPIPE(conn, spinfo, failaction) \
	do { \
		(spinfo).got_epipe = false; \
		if (!SIGPIPE_MASKED(conn)) \
		{ \
			if (pq_block_sigpipe(&(spinfo).oldsigmask, \
								 &(spinfo).sigpipe_pending) < 0) \
				failaction; \
		} \
	} while (0)

#define REMEMBER_EPIPE(spinfo, cond) \
	do { \
		if (cond) \
			(spinfo).got_epipe = true; \
	} while (0)

#define RESTORE_SIGPIPE(conn, spinfo) \
	do { \
		if (!SIGPIPE_MASKED(conn)) \
			pq_reset_sigpipe(&(spinfo).oldsigmask, (spinfo).sigpipe_pending, \
							 (spinfo).got_epipe); \
	} while (0)
#else							/* !ENABLE_THREAD_SAFETY */

#define DECLARE_SIGPIPE_INFO(spinfo) pqsigfunc spinfo = NULL

#define DISABLE_SIGPIPE(conn, spinfo, failaction) \
	do { \
		if (!SIGPIPE_MASKED(conn)) \
			spinfo = pqsignal(SIGPIPE, SIG_IGN); \
	} while (0)

#define REMEMBER_EPIPE(spinfo, cond)

#define RESTORE_SIGPIPE(conn, spinfo) \
	do { \
		if (!SIGPIPE_MASKED(conn)) \
			pqsignal(SIGPIPE, spinfo); \
	} while (0)
#endif							/* 启用线程安全 */
#else							/* WIN32 */

#define DECLARE_SIGPIPE_INFO(spinfo)
#define DISABLE_SIGPIPE(conn, spinfo, failaction)
#define REMEMBER_EPIPE(spinfo, cond)
#define RESTORE_SIGPIPE(conn, spinfo)
#endif							/* WIN32 */

/* ------------------------------------------------------------ */
/*			 所有安全会话的共用过程			*/
/* ------------------------------------------------------------ */


int PQsslInUse(PGconn *fc_conn)
{
	if (!fc_conn)
		return 0;
	return fc_conn->ssl_in_use;
}

/*
 *	导出函数，允许应用程序通知我们它已经
 *	初始化了 OpenSSL。
 */
void PQinitSSL(int fc_do_init)
{
#ifdef USE_SSL
	pgtls_init_library(fc_do_init, fc_do_init);
#endif
}

/*
 *	导出函数，允许应用程序通知我们它已经
 *	初始化了 OpenSSL 和/或 libcrypto。
 */
void PQinitOpenSSL(int fc_do_ssl, int fc_do_crypto)
{
#ifdef USE_SSL
	pgtls_init_library(fc_do_ssl, fc_do_crypto);
#endif
}

/*
 *	初始化全局 SSL 上下文
 */
int pqsecure_initialize(PGconn *fc_conn, bool fc_do_ssl, bool fc_do_crypto)
{
	int			fc_r = 0;

#ifdef USE_SSL
	fc_r = pgtls_init(fc_conn, fc_do_ssl, fc_do_crypto);
#endif

	return fc_r;
}

/*
 * 开始或继续协商安全会话。
 */
PostgresPollingStatusType pqsecure_open_client(PGconn *fc_conn)
{
#ifdef USE_SSL
	return pgtls_open_client(fc_conn);
#else
	/* 不应该到这里 */
	return PGRES_POLLING_FAILED;
#endif
}

/*
 *	关闭安全会话。
 */
void pqsecure_close(PGconn *fc_conn)
{
#ifdef USE_SSL
	pgtls_close(fc_conn);
#endif
}

/*
 *	从安全连接读取数据。
 *
 * 在失败时，此函数负责将适当的消息附加到 conn->errorMessage。调用者仍须检查 errno，但仅仅是为了确定在错误后是否继续/重试。
 */
ssize_t pqsecure_read(PGconn *fc_conn, void *fc_ptr, size_t fc_len)
{
	ssize_t		fc_n;

#ifdef USE_SSL
	if (fc_conn->ssl_in_use)
	{
		fc_n = pgtls_read(fc_conn, fc_ptr, fc_len);
	}
	else
#endif
#ifdef ENABLE_GSS
	if (fc_conn->gssenc)
	{
		fc_n = pg_GSS_read(fc_conn, fc_ptr, fc_len);
	}
	else
#endif
	{
		fc_n = pqsecure_raw_read(fc_conn, fc_ptr, fc_len);
	}

	return fc_n;
}

ssize_t pqsecure_raw_read(PGconn *fc_conn, void *fc_ptr, size_t fc_len)
{
	ssize_t		fc_n;
	int			fc_result_errno = 0;
	char		fc_sebuf[PG_STRERROR_R_BUFLEN];

	SOCK_ERRNO_SET(0);

	fc_n = recv(fc_conn->sock, fc_ptr, fc_len, 0);

	if (fc_n < 0)
	{
		fc_result_errno = SOCK_ERRNO;

		/* 如果合适，则设置错误消息 */
		switch (fc_result_errno)
		{
#ifdef EAGAIN
			case EAGAIN:
#endif
#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
			case EWOULDBLOCK:
#endif
			case EINTR:
				/* 没有错误消息，期望调用者重试 */
				break;

			case EPIPE:
			case ECONNRESET:
				appendPQExpBufferStr(&fc_conn->errorMessage,
									 libpq_gettext("server closed the connection unexpectedly\n"
												   "\tThis probably means the server terminated abnormally\n"
												   "\tbefore or while processing the request.\n"));
				break;

			case 0:
				/* 如果 errno 没有被设置，将其视为常规 EOF */
				fc_n = 0;
				break;

			default:
				appendPQExpBuffer(&fc_conn->errorMessage,
								  libpq_gettext("could not receive data from server: %s\n"),
								  SOCK_STRERROR(fc_result_errno,
												fc_sebuf, sizeof(fc_sebuf)));
				break;
		}
	}

	/* 确保我们将预期的errno返回给调用者 */
	SOCK_ERRNO_SET(fc_result_errno);

	return fc_n;
}

/*
 *	将数据写入安全连接。
 *
 * 返回写入的字节数，或在失败时返回负值（并设置 errno）。写入的字节数可能少于请求的数量。
 *
 * 请注意，套接字级的严重故障对调用者是隐藏的，而是设置 conn->write_failed 并存储错误消息在 conn->write_err_msg 中；见 pqsecure_raw_write。这使我们能够推迟报告写入失败，直到我们确认服务器没有可用的错误消息。
 *
 * 但是，在 SSL 或 GSS 管理级别检测到的错误通过负值结果报告，并将消息附加到 conn->errorMessage。通常不清楚这些错误应被视为读取错误还是写入错误，因此我们不试图推迟报告它们。
 *
 * 失败时，调用者仍然必须检查 errno，但仅用于确定是否继续/重试；无论如何，某个地方已经保存了一条消息。
 */
ssize_t pqsecure_write(PGconn *fc_conn, const void *fc_ptr, size_t fc_len)
{
	ssize_t		fc_n;

#ifdef USE_SSL
	if (fc_conn->ssl_in_use)
	{
		fc_n = pgtls_write(fc_conn, fc_ptr, fc_len);
	}
	else
#endif
#ifdef ENABLE_GSS
	if (fc_conn->gssenc)
	{
		fc_n = pg_GSS_write(fc_conn, fc_ptr, fc_len);
	}
	else
#endif
	{
		fc_n = pqsecure_raw_write(fc_conn, fc_ptr, fc_len);
	}

	return fc_n;
}

/*
 * pqsecure_write的低级实现。
 *
 * 这直接用于未加密的连接。对于加密连接，这为pgtls_write或
 * pg_GSS_write进行实际的I/O。
 *
 * 此函数仅对可重试错误（例如EINTR）报告失败（即返回负结果）。
 * 对于此类情况的循环处理应在外部层次进行，也许一直到应用程序。
 * 对于严重失败，我们设置conn->write_failed并将错误消息存储
 * 在conn->write_err_msg中，但随后仍声称已成功写入数据。
 * 这是因为只要有可能从服务器读取并获取能够解释连接中断的
 * 错误消息，我们就不想报告写入失败。许多TCP栈存在竞争条件，
 * 以至于写入失败可能在所有传入数据都被读取之前报告，也可能
 * 不会被报告。
 *
 * 请注意，当我们使用SSL时，这种错误行为发生在SSL管理级别之下。
 * 这是因为至少某些版本的OpenSSL在仍然有可能从服务器获取更
 * 有用的错误消息时，会过快地报告写入失败。
 */
ssize_t pqsecure_raw_write(PGconn *fc_conn, const void *fc_ptr, size_t fc_len)
{
	ssize_t		fc_n;
	int			fc_flags = 0;
	int			fc_result_errno = 0;
	char		fc_msgbuf[1024];
	char		fc_sebuf[PG_STRERROR_R_BUFLEN];

	DECLARE_SIGPIPE_INFO(fc_spinfo);

	/*
	 * 如果我们已经发生了写入失败，我们将不会再尝试在该连接上
	 * 发送数据。即使内核允许我们，我们也可能已经与服务器失去
	 * 消息边界的同步。conn->write_failed因此持续存在，直到连接
	 * 被重置，并且我们会丢弃所有试图写入的数据。
	 */
	if (fc_conn->write_failed)
		return fc_len;

#ifdef MSG_NOSIGNAL
	if (fc_conn->sigpipe_flag)
		fc_flags |= MSG_NOSIGNAL;

retry_masked:
#endif							/* MSG_NOSIGNAL */

	DISABLE_SIGPIPE(fc_conn, fc_spinfo, return -1);

	fc_n = send(fc_conn->sock, fc_ptr, fc_len, fc_flags);

	if (fc_n < 0)
	{
		fc_result_errno = SOCK_ERRNO;

		/*
		 * 如果我们看到EINVAL，可能是因为此机器上没有MSG_NOSIGNAL。
		 * 所以，清除sigpipe_flag以避免再次尝试该标志，并重试send()。
		 */
#ifdef MSG_NOSIGNAL
		if (fc_flags != 0 && fc_result_errno == EINVAL)
		{
			fc_conn->sigpipe_flag = false;
			fc_flags = 0;
			goto retry_masked;
		}
#endif							/* MSG_NOSIGNAL */

		/* 如果合适，则设置错误消息 */
		switch (fc_result_errno)
		{
#ifdef EAGAIN
			case EAGAIN:
#endif
#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
			case EWOULDBLOCK:
#endif
			case EINTR:
				/* 没有错误消息，期望调用者重试 */
				break;

			case EPIPE:
				/* 为EPIPE设置标志 */
				REMEMBER_EPIPE(fc_spinfo, true);

				/* FALL THRU */

			case ECONNRESET:
				fc_conn->write_failed = true;
				/*
 * pqWait: 等待直到我们可以读取或写入连接套接字
 *
 * JAB: 如果启用并使用SSL并且用于读取，缓冲的字节会短路调用select()。
 *
 * 如果内核在套接字上标记了异常条件，我们也会停止等待并返回。 实际的错误条件将在调用者尝试读取或写入套接字时被检测并报告。
 */
				/*
 * pqWaitTimed: 等待，但不超过finish_time。
 *
 * finish_time = ((time_t) -1) 禁用等待限制。
 *
 * 失败时返回-1，如果套接字可读/可写则返回0，如果超时则返回1。
 */
				snprintf(fc_msgbuf, sizeof(fc_msgbuf),
						 libpq_gettext("server closed the connection unexpectedly\n"
									   "\tThis probably means the server terminated abnormally\n"
									   "\tbefore or while processing the request.\n"));
				fc_conn->write_err_msg = strdup(fc_msgbuf);
				/* 现在声称写入成功 */
				fc_n = fc_len;
				break;

			default:
				fc_conn->write_failed = true;
				/*
 * pqWait: 等待直到我们可以读取或写入连接套接字
 *
 * JAB: 如果启用并使用SSL并且用于读取，缓冲的字节会短路调用select()。
 *
 * 如果内核在套接字上标记了异常条件，我们也会停止等待并返回。 实际的错误条件将在调用者尝试读取或写入套接字时被检测并报告。
 */
				/*
 * pqWaitTimed: 等待，但不超过finish_time。
 *
 * finish_time = ((time_t) -1) 禁用等待限制。
 *
 * 失败时返回-1，如果套接字可读/可写则返回0，如果超时则返回1。
 */
				snprintf(fc_msgbuf, sizeof(fc_msgbuf),
						 libpq_gettext("could not send data to server: %s\n"),
						 SOCK_STRERROR(fc_result_errno,
									   fc_sebuf, sizeof(fc_sebuf)));
				fc_conn->write_err_msg = strdup(fc_msgbuf);
				/* 现在声称写入成功 */
				fc_n = fc_len;
				break;
		}
	}

	RESTORE_SIGPIPE(fc_conn, fc_spinfo);

	/* 确保我们将预期的errno返回给调用者 */
	SOCK_ERRNO_SET(fc_result_errno);

	return fc_n;
}

/* 在没有SSL支持时的SSL信息函数的虚拟版本 */
#ifndef USE_SSL

void * PQgetssl(PGconn *fc_conn)
{
	return NULL;
}

void * PQsslStruct(PGconn *fc_conn, const char *fc_struct_name)
{
	return NULL;
}

const char * PQsslAttribute(PGconn *fc_conn, const char *fc_attribute_name)
{
	return NULL;
}

const char *const *
PQsslAttributeNames(PGconn *fc_conn)
{
	static const char *const fc_result[] = {NULL};

	return fc_result;
}
#endif							/* USE_SSL */

/*
 * 在没有OpenSSL时，OpenSSL密钥密码挂钩函数的虚拟版本。
 */
#ifndef USE_OPENSSL

PQsslKeyPassHook_OpenSSL_type
PQgetSSLKeyPassHook_OpenSSL(void)
{
	return NULL;
}

void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type fc_hook)
{
	return;
}

int PQdefaultSSLKeyPassHook_OpenSSL(char *fc_buf, int fc_size, PGconn *fc_conn)
{
	return 0;
}
#endif							/* USE_OPENSSL */

/* 在没有GSS支持时的GSSAPI信息函数的虚拟版本 */
#ifndef ENABLE_GSS

void * PQgetgssctx(PGconn *fc_conn)
{
	return NULL;
}

int PQgssEncInUse(PGconn *fc_conn)
{
	return 0;
}

#endif							/* ENABLE_GSS */


#if defined(ENABLE_THREAD_SAFETY) && !defined(WIN32)

/*
 * 阻止该线程的SIGPIPE。这防止send()/write()退出应用程序。
 */
int pq_block_sigpipe(sigset_t *fc_osigset, bool *fc_sigpipe_pending)
{
	sigset_t	fc_sigpipe_sigset;
	sigset_t	fc_sigset;

	sigemptyset(&fc_sigpipe_sigset);
	sigaddset(&fc_sigpipe_sigset, SIGPIPE);

	/* 阻止SIGPIPE并保存之前的掩码以便稍后重置 */
	SOCK_ERRNO_SET(pthread_sigmask(SIG_BLOCK, &fc_sigpipe_sigset, fc_osigset));
	if (SOCK_ERRNO)
		return -1;

	/* 只有在之前被阻塞的情况下，我们才可能有待处理的SIGPIPE */
	if (sigismember(fc_osigset, SIGPIPE))
	{
		/* 是否存在待处理的SIGPIPE？ */
		if (sigpending(&fc_sigset) != 0)
			return -1;

		if (sigismember(&fc_sigset, SIGPIPE))
			*fc_sigpipe_pending = true;
		else
			*fc_sigpipe_pending = false;
	}
	else
		*fc_sigpipe_pending = false;

	return 0;
}

/*
 * 丢弃任何待处理的 SIGPIPE 信号并重置信号掩码。
 *
 * 注意：我们在这里实际上假设 C 库不会排队多个 SIGPIPE 事件。 如果有，那么当一个事件已在待处理状态而我们又收到了另一个时，我们将不小心将我们的事件留在队列中。 只要它不排队多个事件，我们就没问题，因为调用者无法分辨。
 *
 * 如果调用者确定没有收到 EPIPE 错误，应该将 got_epipe 设置为 false；在这种情况下，我们将跳过清除操作，事情肯定没问题，无论是否排队。 如果收到了一个或可能收到了一个，则将 got_epipe 设置为 true。
 *
 * 我们不希望这改变 errno，因为如果这样做可能会丢失前一个 send() 的错误代码。 我们基本上假设，如果我们能够做 pq_block_sigpipe()，这不会失败。
 */
void pq_reset_sigpipe(sigset_t *fc_osigset, bool fc_sigpipe_pending, bool fc_got_epipe)
{
	int			fc_save_errno = SOCK_ERRNO;
	int			fc_signo;
	sigset_t	fc_sigset;

	/* 仅当没有待处理时清除 SIGPIPE */
	if (fc_got_epipe && !fc_sigpipe_pending)
	{
		if (sigpending(&fc_sigset) == 0 &&
			sigismember(&fc_sigset, SIGPIPE))
		{
			sigset_t	fc_sigpipe_sigset;

			sigemptyset(&fc_sigpipe_sigset);
			sigaddset(&fc_sigpipe_sigset, SIGPIPE);

			sigwait(&fc_sigpipe_sigset, &fc_signo);
		}
	}

	/* 恢复保存的阻塞掩码 */
	pthread_sigmask(SIG_SETMASK, fc_osigset, NULL);

	SOCK_ERRNO_SET(fc_save_errno);
}

#endif							/* ENABLE_THREAD_SAFETY && !WIN32 */
